package io.adrainty.bolee.file.handler.oss;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.internal.OSSConstants;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.*;
import com.aliyuncs.exceptions.ClientException;
import io.adrainty.bolee.file.handler.AbsFileStorageHandler;
import io.adrainty.bolee.file.models.entity.FileEntity;
import io.adrainty.bolee.file.models.valobj.FileStorageEnum;
import io.adrainty.bolee.framework.commons.basic.ResponseCode;
import io.adrainty.bolee.framework.commons.exception.AppException;
import io.adrainty.bolee.framework.commons.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.List;

/**
 * <p>OssFileStorageHandler</p>
 * 这里BucketName全部用自定义的BucketName 就不根据传进来的了
 *
 * @author AdRainty
 * @version V1.0.0
 * @description OssFileStorageHandler
 * @since 2025/8/18 11:45:48
 */

@Slf4j
@Component
@SuppressWarnings("unused")
@EnableConfigurationProperties(OssConfigProperties.class)
@ConditionalOnProperty(prefix = "itheima.framework.storage.oss", name = "enable", havingValue = "true")
public class OssFileStorageHandler extends AbsFileStorageHandler {

    private final OSS ossClient;

    private final OssConfigProperties properties;

    public OssFileStorageHandler(OssConfigProperties properties) {
        this.properties = properties;

        // 创建 ClientBuilderConfiguration 实例，用于配置 OSS 客户端参数
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        // 设置签名算法版本为 V4
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        // 设置使用 HTTPS 协议访问 OSS，保证传输安全性
        clientBuilderConfiguration.setProtocol(Protocol.HTTPS);

        // 创建 OSS 客户端实例
        this.ossClient = OSSClientBuilder.create()
                // 以华东1（杭州）地域的外网访问域名为例，Endpoint填写为oss-cn-hangzhou.aliyuncs.com
                .endpoint(properties.getEndpoint())
                // 从环境变量中获取访问凭证（需提前配置 OSS_ACCESS_KEY_ID 和 OSS_ACCESS_KEY_SECRET）
                .credentialsProvider(CredentialsProviderFactory.newDefaultCredentialProvider(properties.getAccessKeyId(), properties.getAccessKeySecret()))
                // 设置客户端配置
                .clientConfiguration(clientBuilderConfiguration)
                // 以华东1（杭州）地域为例，Region填写为cn-hangzhou
                .region(properties.getRegion())
                .build();
    }

    @Override
    public String uploadFile(String suffix, String filename, String bucketName, boolean autoCatalog, InputStream inputStream) {
        // 是否自动生成存储路径并设置文件路径和名称（Key）
        String key = autoCatalog ? buildDirPath(filename) : filename;
        log.info("OSS文件上传开始：{}", key);
        try {
            // 上传文件元数据处理
            ObjectMetadata objectMeta = fileMetaHandler(suffix);
            // 文件上传请求对象
            PutObjectRequest request = new PutObjectRequest(properties.getBucketName(), key, inputStream, objectMeta);
            // 上传限流
            if (commonProperties.isLimitSpeed()) {
                request.setTrafficLimit(commonProperties.getUploadLimitSpeed());
            }
            // 文件上传
            ossClient.putObject(request);
        } catch (Exception oe) {
            throw handleOSSException(oe);
        }
        return key;
    }

    @Override
    public FileEntity initiateMultipartUpload(String suffix, String filename, String bucketName, boolean autoCatalog) {
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, filename);
        // 如果需要在初始化分片时设置请求头，请参考以下示例代码。
        ObjectMetadata metadata = fileMetaHandler(suffix);
        metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
        // 指定该Object的网页缓存行为。
        metadata.setCacheControl("no-cache");
        // 指定该Object被下载时的名称。
        metadata.setContentDisposition("inline;filename=" + filename);
        // 指定该Object的内容编码格式。
        metadata.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
        // 指定请求
        request.setObjectMetadata(metadata);
        // 初始化分片。
        InitiateMultipartUploadResult upResult = ossClient.initiateMultipartUpload(request);
        return FileEntity.builder().bucketName(properties.getBucketName())
                .pathUrl(filename).fileName(filename)
                .uploadId(upResult.getUploadId())
                .build();
    }

    @Override
    public String uploadPart(String upLoadId, String filename, int partNumber, long partSize, String bucketName, InputStream inputStream) {
        // 封装分片上传请求
        UploadPartRequest uploadPartRequest = new UploadPartRequest();
        uploadPartRequest.setUploadId(upLoadId);
        // part大小 1-10000
        uploadPartRequest.setPartNumber(partNumber);
        uploadPartRequest.setPartSize(partSize);
        // 文件上传的bucketName
        uploadPartRequest.setBucketName(properties.getBucketName());
        // 分片文件
        uploadPartRequest.setInputStream(inputStream);
        uploadPartRequest.setKey(filename);
        // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
        try {
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
            log.info("[{}] {}文件第{}片上传成功",
                    uploadPartResult.getRequestId(), upLoadId, uploadPartRequest.getPartNumber());
            return JsonUtil.toJson(uploadPartResult.getPartETag());
        } catch (Exception e) {
            throw handleOSSException(e);
        }
    }

    @Override
    public String completeMultipartUpload(String upLoadId, List<String> partETags, String filename, String bucketName) {
        StopWatch st = new StopWatch();
        st.start();
        List<PartETag> partETagList = partETags.stream()
                .map(part -> JsonUtil.fromJson(part, PartETag.class)).toList();
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(properties.getBucketName(), filename, upLoadId, partETagList);
        log.info("{}文件上传完成,开始合并, partList:{}", upLoadId, partETags);
        try {
            CompleteMultipartUploadResult completeMultipartUploadResult =
                    ossClient.completeMultipartUpload(completeMultipartUploadRequest);
            st.stop();
            log.info("{}文件上传合并成功, 耗时:{}", upLoadId, st.getTotalTimeMillis());
            return completeMultipartUploadResult.getETag();
        } catch (Exception e) {
            st.stop();
            log.error("{}文件上传合并失败, 耗时:{}", upLoadId, st.getTotalTimeMillis());
            throw handleOSSException(e);
        }
    }

    @Override
    public InputStream downloadFile(String bucketName, String pathUrl) {
        GetObjectRequest request = new GetObjectRequest(properties.getBucketName(), pathUrl);
        // 下载传限流
        if (commonProperties.isLimitSpeed()) {
            request.setTrafficLimit(commonProperties.getDownloadLimitSpeed());
        }
        return ossClient.getObject(request).getObjectContent();
    }

    @Override
    public void delete(String bucketName, String pathUrl) {
        ossClient.deleteObject(properties.getBucketName(), pathUrl);
    }

    @Override
    public void deleteBatch(String bucketName, List<String> pathUrls) {
        ossClient.deleteObjects(
                new DeleteObjectsRequest(properties.getBucketName()).withKeys(pathUrls)
        );
    }

    @Override
    public String getReadablePath(String bucketName, String pathUrl) {
        Date expiration = new Date(new Date().getTime() + commonProperties.getTempFileReadTimeout());
        try {
            URL url = ossClient.generatePresignedUrl(properties.getBucketName(), pathUrl, expiration);
            return url.toString();
        } catch (Exception e) {
            throw handleOSSException(e);
        }
    }

    @Override
    public FileStorageEnum supportStorage() {
        return FileStorageEnum.ALIYUN_OSS;
    }

    private ObjectMetadata fileMetaHandler(String prefix) {
        // 元数据对象
        ObjectMetadata objectMeta = new ObjectMetadata();
        // 文件字符集
        objectMeta.setContentEncoding("UTF-8");
        // 文件类型匹配
        objectMeta.setContentType(metaMimeTypeMap.get(prefix.toLowerCase()));
        return objectMeta;
    }

    private AppException handleOSSException(Exception exp) {
        if (exp instanceof OSSException oe) {
            // 当请求成功到达OSS服务端, 但服务端拒绝执行请求时抛出此异常
            // 常见原因：权限不足、参数错误、资源不存在等
            log.error("OSSException: [{}]({}){}", oe.getRequestId(), oe.getErrorCode(), oe.getErrorMessage());
            return new AppException(ResponseCode.FILE_UPLOAD_ERROR);
        } else if (exp instanceof ClientException ce) {
            // 当客户端在与OSS通信过程中遇到严重内部问题时抛出此异常
            // 常见原因：网络连接问题、SSL证书问题、DNS解析失败等
            log.error("ClientException: [{}]{}", ce.getRequestId(), ce.getMessage());
            return new AppException(ResponseCode.RETRY_LATER);
        } else {
            log.error("Unknown Exception: ", exp);
            return new AppException(ResponseCode.FILE_UPLOAD_ERROR);
        }
    }

}
